﻿using System;
using System.Collections.Generic;
using System.Linq;
using CID.Domain.Development;
using CID.Domain.Operations;
using CID.Execution.Controller.Support;
using CID.Execution.Support.Scripting;

namespace CID.Execution.Controller
{
    class DeploymentState : IDeploymentState
    {
        private List<int> locationsWithErrors;

        public IEnumerable<LocationActivationScript> ActivationScripts { get; private set; }
        public IEnumerable<LocationDeactivationScript> DeactivationScripts { get; private set; }
        public IEnumerable<LocationDeploymentScript> DeploymentScripts { get; private set; }
        public IEnumerable<int> LocationsWithErrors { get { return locationsWithErrors; } }
        public int PendingActivations { get; private set; }
        public int PendingDeactivations { get; private set; }
        public int PendingDeployments { get; private set; }
        public Guid DeploymentUniqueIdentifier { get; private set; }
        public int ActivationErrors { get; private set; }
        public int DeactivationErrors { get; private set; }
        public int DeploymentErrors { get; private set; }

        public DeploymentState(Guid deploymentUniqueIdentifier, IRelease release, IEnvironment targetEnvironment)
        {
            DeploymentUniqueIdentifier = deploymentUniqueIdentifier;

            var affectedVersions = new AffectedVersionsResolver(release, targetEnvironment).GetAffectedVersions();
            var affectedComponentsByLocations = ResolveLocationsComponentsMap(affectedVersions, targetEnvironment);
            var releasedComponentsByLocations = ResolveLocationsComponentsMap(release.Versions, targetEnvironment);
            
            var componentDependencyResolver = new ComponentDependencyResolver(affectedVersions);
            var deactivationMasterScript = new MasterDeactivationScript(DeploymentUniqueIdentifier, release, componentDependencyResolver.GetDeactivationSequence(), targetEnvironment);
            var deploymentMasterScript = new MasterDeploymentScript(DeploymentUniqueIdentifier, release, release.Versions.OrderBy(x => x.Component.Name), targetEnvironment);
            var activationMasterScript = new MasterActivationScript(DeploymentUniqueIdentifier, release, componentDependencyResolver.GetActivationSequence(), targetEnvironment);

            var activationScripts = new List<LocationActivationScript>();
            var deactivationScripts = new List<LocationDeactivationScript>();
            var deploymentScripts = new List<LocationDeploymentScript>();

            foreach (var item in affectedComponentsByLocations)
            {
                activationScripts.Add(activationMasterScript.CreateLocationSpecific(item.Key, item.Value));
                deactivationScripts.Add(deactivationMasterScript.CreateLocationSpecific(item.Key, item.Value));
            }
            foreach (var item in releasedComponentsByLocations)
                deploymentScripts.Add(deploymentMasterScript.CreateLocationSpecific(item.Key, item.Value));

            ActivationScripts = activationScripts;
            DeactivationScripts = deactivationScripts;
            DeploymentScripts = deploymentScripts;
            PendingActivations = PendingDeactivations = affectedComponentsByLocations.Count;
            PendingDeployments = releasedComponentsByLocations.Count;
            locationsWithErrors = new List<int>();
        }

        private IDictionary<ILocation, List<IComponent>> ResolveLocationsComponentsMap(IEnumerable<IVersion> involvedVersions, IEnvironment targetEnvironment)
        {
            var involvedComponents = involvedVersions.Select(x => x.Component);
            var requiredServerRoles = involvedComponents.Select(x => x.RequiredServerRole).Distinct().ToList();
            var involvedServers = targetEnvironment.Assignments.Where(x => requiredServerRoles.Contains(x.ServerRole)).Select(x => x.Server);

            var result = new Dictionary<ILocation, List<IComponent>>();
            foreach (var server in involvedServers)
            {
                if (!result.ContainsKey(server.Location)) result.Add(server.Location, new List<IComponent>());

                foreach (var serverRole in server.Assignments.Where(x => x.Environment.Equals(targetEnvironment)).Select(x => x.ServerRole))
                    result[server.Location].AddRange(involvedComponents.Where(x => x.RequiredServerRole.Equals(serverRole)).Distinct());
            }

            return result;
        }

        public int NotifyActivationCompleted(int locationId, bool error)
        {
            int result;
            lock (this) 
            { 
                result = --PendingActivations;
                if (error)
                {
                    AddLocationWithError(locationId);
                    ActivationErrors++;
                }
            }
            return result;
        }

        public int NotifyDeactivationCompleted(int locationId, bool error)
        {
            int result;
            lock (this) 
            { 
                result = --PendingDeactivations;
                if (error)
                {
                    AddLocationWithError(locationId);
                    DeactivationErrors++;
                }
            }
            return result;
        }

        public int NotifyDeploymentCompleted(int locationId, bool error)
        {
            int result;
            lock (this) 
            { 
                result = --PendingDeployments;
                if (error)
                {
                    AddLocationWithError(locationId);
                    DeploymentErrors++;
                }
            }
            return result;
        }

        public bool Successful
        {
            get { return ((ActivationErrors + DeactivationErrors + DeploymentErrors) == 0); }
        }

        private void AddLocationWithError(int locationId)
        {
            if (!locationsWithErrors.Contains(locationId))
                locationsWithErrors.Add(locationId);
        }

        public bool LocationHasErrors(int locationId)
        {
            return locationsWithErrors.Contains(locationId);
        }
    }
}
